package top.z.template.parse.ast.expression;

import top.z.template.Context;
import top.z.template.Error;
import top.z.template.Template;
import top.z.template.parse.Ast.Expression;
import top.z.template.parse.Token;
import top.z.template.parse.TokenType;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 二元操作表示算术运算符, 如加法或除法, 比较运算符, 如小于或等于, 逻辑运算符，如与(and)或者(or), 以及赋值操作.
 */
public class BinaryOperation extends Expression {

    public enum BinaryOperator {
        // ----算数运算----
        Plus,     // +
        Minus,    // -
        Divide,   // /
        Percent,  // %
        Multiply, // *
        // ----逻辑运算----
        EQ,  // ==
        NQ,  // !=
        LT,  // <
        LE,  // <=
        GT,  // >
        GE,  // >=
        Or,  // ||
        And, // &&
        Xor, // ^
        // ----赋值运算----
        Assignment, // =
        ;

        public static BinaryOperator getOperator(Token token) {
            // 算数运算
            if (token.getType() == TokenType.Plus) return BinaryOperator.Plus;
            if (token.getType() == TokenType.Minus) return BinaryOperator.Minus;
            if (token.getType() == TokenType.Divide) return BinaryOperator.Divide;
            if (token.getType() == TokenType.Percent) return BinaryOperator.Percent;
            if (token.getType() == TokenType.Multiply) return BinaryOperator.Multiply;
            // 逻辑运算
            if (token.getType() == TokenType.EQ) return BinaryOperator.EQ;
            if (token.getType() == TokenType.NQ) return BinaryOperator.NQ;
            if (token.getType() == TokenType.LT) return BinaryOperator.LT;
            if (token.getType() == TokenType.LE) return BinaryOperator.LE;
            if (token.getType() == TokenType.GT) return BinaryOperator.GT;
            if (token.getType() == TokenType.GE) return BinaryOperator.GE;
            if (token.getType() == TokenType.Or) return BinaryOperator.Or;
            if (token.getType() == TokenType.And) return BinaryOperator.And;
            if (token.getType() == TokenType.Xor) return BinaryOperator.Xor;
            // 赋值运算
            if (token.getType() == TokenType.Assignment) return BinaryOperator.Assignment;

            throw Error.error("Unknown binary operator " + token + ".", token.getSpan());
        }

    }

    private final Expression     leftOperand;
    private final BinaryOperator operator;
    private final Expression     rightOperand;

    public BinaryOperation(Expression leftOperand, Token operator, Expression rightOperand) {
        super(operator.getSpan());
        this.leftOperand = leftOperand;
        this.operator = BinaryOperator.getOperator(operator);
        this.rightOperand = rightOperand;
    }

    public Expression getLeftOperand() {
        return leftOperand;
    }

    public BinaryOperator getOperator() {
        return operator;
    }

    public Expression getRightOperand() {
        return rightOperand;
    }

    private Object evaluatePlus(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() + ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() + ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() + ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() + ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() + ((Number) right).doubleValue();
        }
        if (left instanceof String || right instanceof String) {
            return left.toString() + right.toString();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() + ((Number) right).intValue();
        }

        throw Error.error("Operands for addition operator must be numbers or strings, got " + left + ", " + right + ".", getSpan());
    }

    private Object evaluateMinus(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() - ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() - ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() - ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() - ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() - ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() - ((Number) right).intValue();
        }

        throw Error.error("Operands for subtraction operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateMultiply(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() * ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() * ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() * ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() * ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() * ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() * ((Number) right).intValue();
        }

        throw Error.error("Operands for multiplication operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateDivide(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() / ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() / ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() / ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() / ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() / ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() / ((Number) right).intValue();
        }

        throw Error.error("Operands for division operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluatePercent(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() % ((Number) right).byteValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() % ((Number) right).shortValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() % ((Number) right).longValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() % ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() % ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() % ((Number) right).intValue();
        }

        throw Error.error("Operands for modulo operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private boolean evaluateLT(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() < ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() < ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() < ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() < ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() < ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() < ((Number) right).intValue();
        }

        throw Error.error("Operands for less operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateLE(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() <= ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() <= ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() <= ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() <= ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() <= ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() <= ((Number) right).intValue();
        }

        throw Error.error("Operands for less/equal operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateGT(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() > ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() > ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() > ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() > ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() > ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() > ((Number) right).intValue();
        }

        throw Error.error("Operands for greater operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateGE(Object left, Object right) {
        if (left instanceof Byte || right instanceof Byte) {
            return ((Number) left).byteValue() >= ((Number) right).byteValue();
        }
        if (left instanceof Long || right instanceof Long) {
            return ((Number) left).longValue() >= ((Number) right).longValue();
        }
        if (left instanceof Short || right instanceof Short) {
            return ((Number) left).shortValue() >= ((Number) right).shortValue();
        }
        if (left instanceof Float || right instanceof Float) {
            return ((Number) left).floatValue() >= ((Number) right).floatValue();
        }
        if (left instanceof Double || right instanceof Double) {
            return ((Number) left).doubleValue() >= ((Number) right).doubleValue();
        }
        if (left instanceof Integer || right instanceof Integer) {
            return ((Number) left).intValue() >= ((Number) right).intValue();
        }

        throw Error.error("Operands for greater/equal operator must be numbers" + left + ", " + right + ".", getSpan());
    }

    private Object evaluateAnd(Object left, Template template, Context context, OutputStream out) throws IOException {
        if (!(left instanceof Boolean)) {
            throw Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
        }

        Object right = getRightOperand().evaluate(template, context, out);
        if (!(right instanceof Boolean)) {
            throw Error.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan());
        }
        return Boolean.parseBoolean(left.toString()) && Boolean.parseBoolean(right.toString());
    }

    private Object evaluateOr(Object left, Template template, Context context, OutputStream out) throws IOException {
        Object right = getRightOperand().evaluate(template, context, out);
        if (!(right instanceof Boolean)) {
            throw Error.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan());
        }

        if (!(left instanceof Boolean)) {
            throw Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
        }
        return Boolean.parseBoolean(left.toString()) || Boolean.parseBoolean(right.toString());
    }

    private Object evaluateXor(Object left, Object right) {
        if (!(left instanceof Boolean)) {
            throw Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
        }
        if (!(right instanceof Boolean)) {
            throw Error.error("Right operand must be a boolean, got " + right + ".", getRightOperand().getSpan());
        }
        return (Boolean) left ^ (Boolean) right;
    }

    private Object evaluateEQ(Object left, Object right) {
        if (!(left instanceof Boolean)) {
            throw Error.error("Left operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
        }
        if (!(right instanceof Boolean)) {
            throw Error.error("Right operand must be a boolean, got " + left + ".", getLeftOperand().getSpan());
        }
        return left.equals(right);
    }

    private Object evaluateNQ(Object left, Object right) {
        return !(Boolean) evaluateEQ(left, right);
    }

    @Override
    public Object evaluate(Template template, Context context, OutputStream out) throws IOException {
        if (getOperator() == BinaryOperator.Assignment) {
            if (!(getLeftOperand() instanceof Variable)) {
                throw Error.error("Can only assign to top-level variables in context.", getLeftOperand().getSpan());
            }
            Object value = getRightOperand().evaluate(template, context, out);
            context.set(((Variable) getLeftOperand()).getVariableName().getText(), value);
            return null;
        }

        Object left  = getLeftOperand().evaluate(template, context, out);
        Object right = getOperator() == BinaryOperator.And || getOperator() == BinaryOperator.Or ? null : getRightOperand().evaluate(template, context, out);

        switch (getOperator()) {
            case Plus:
                return evaluatePlus(left, right);
            case Minus:
                return evaluateMinus(left, right);
            case Divide:
                return evaluateDivide(left, right);
            case Multiply:
                return evaluateMultiply(left, right);
            case Percent:
                return evaluatePercent(left, right);
            case LT:
                return evaluateLT(left, right);
            case LE:
                return evaluateLE(left, right);
            case GT:
                return evaluateGT(left, right);
            case GE:
                return evaluateGE(left, right);
            case EQ:
                return evaluateEQ(left, right);
            case NQ:
                return evaluateNQ(left, right);
            case And:
                return evaluateAnd(left, template, context, out);
            case Or:
                return evaluateOr(left, template, context, out);
            case Xor:
                return evaluateXor(left, right);
            default:
                throw Error.error("Binary operator " + getOperator().name() + " not implemented", getSpan());
        }
    }
}